// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/service/memory_program_cache.h"

#include "base/base64.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/metrics/histogram.h"
#include "base/sha1.h"
#include "base/strings/string_number_conversions.h"
#include "gpu/command_buffer/common/constants.h"
//#include "gpu/command_buffer/service/disk_cache_proto.pb.h"
#include "gen/protoc_out/gpu/command_buffer/service/disk_cache_proto.pb.h"
#include "gpu/command_buffer/service/gl_utils.h"
#include "gpu/command_buffer/service/gles2_cmd_decoder.h"
#include "gpu/command_buffer/service/gpu_switches.h"
#include "gpu/command_buffer/service/shader_manager.h"
#include "ui/gl/gl_bindings.h"

namespace {

size_t GetCacheSizeBytes()
{
    const base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
    if (command_line->HasSwitch(switches::kGpuProgramCacheSizeKb)) {
        size_t size;
        if (base::StringToSizeT(
                command_line->GetSwitchValueNative(switches::kGpuProgramCacheSizeKb),
                &size))
            return size * 1024;
    }
    return gpu::kDefaultMaxProgramCacheMemoryBytes;
}

} // anonymous namespace

namespace gpu {
namespace gles2 {

    namespace {

        enum ShaderMapType {
            ATTRIB_MAP = 0,
            UNIFORM_MAP,
            VARYING_MAP
        };

        void FillShaderVariableProto(
            ShaderVariableProto* proto, const sh::ShaderVariable& variable)
        {
            proto->set_type(variable.type);
            proto->set_precision(variable.precision);
            proto->set_name(variable.name);
            proto->set_mapped_name(variable.mappedName);
            proto->set_array_size(variable.arraySize);
            proto->set_static_use(variable.staticUse);
            for (size_t ii = 0; ii < variable.fields.size(); ++ii) {
                ShaderVariableProto* field = proto->add_fields();
                FillShaderVariableProto(field, variable.fields[ii]);
            }
            proto->set_struct_name(variable.structName);
        }

        void FillShaderAttributeProto(
            ShaderAttributeProto* proto, const sh::Attribute& attrib)
        {
            FillShaderVariableProto(proto->mutable_basic(), attrib);
            proto->set_location(attrib.location);
        }

        void FillShaderUniformProto(
            ShaderUniformProto* proto, const sh::Uniform& uniform)
        {
            FillShaderVariableProto(proto->mutable_basic(), uniform);
        }

        void FillShaderVaryingProto(
            ShaderVaryingProto* proto, const sh::Varying& varying)
        {
            FillShaderVariableProto(proto->mutable_basic(), varying);
            proto->set_interpolation(varying.interpolation);
            proto->set_is_invariant(varying.isInvariant);
        }

        void FillShaderProto(ShaderProto* proto, const char* sha,
            const Shader* shader)
        {
            proto->set_sha(sha, gpu::gles2::ProgramCache::kHashLength);
            for (AttributeMap::const_iterator iter = shader->attrib_map().begin();
                 iter != shader->attrib_map().end(); ++iter) {
                ShaderAttributeProto* info = proto->add_attribs();
                FillShaderAttributeProto(info, iter->second);
            }
            for (UniformMap::const_iterator iter = shader->uniform_map().begin();
                 iter != shader->uniform_map().end(); ++iter) {
                ShaderUniformProto* info = proto->add_uniforms();
                FillShaderUniformProto(info, iter->second);
            }
            for (VaryingMap::const_iterator iter = shader->varying_map().begin();
                 iter != shader->varying_map().end(); ++iter) {
                ShaderVaryingProto* info = proto->add_varyings();
                FillShaderVaryingProto(info, iter->second);
            }
        }

        void RetrieveShaderVariableInfo(
            const ShaderVariableProto& proto, sh::ShaderVariable* variable)
        {
            variable->type = proto.type();
            variable->precision = proto.precision();
            variable->name = proto.name();
            variable->mappedName = proto.mapped_name();
            variable->arraySize = proto.array_size();
            variable->staticUse = proto.static_use();
            variable->fields.resize(proto.fields_size());
            for (int ii = 0; ii < proto.fields_size(); ++ii)
                RetrieveShaderVariableInfo(proto.fields(ii), &(variable->fields[ii]));
            variable->structName = proto.struct_name();
        }

        void RetrieveShaderAttributeInfo(
            const ShaderAttributeProto& proto, AttributeMap* map)
        {
            sh::Attribute attrib;
            RetrieveShaderVariableInfo(proto.basic(), &attrib);
            attrib.location = proto.location();
            (*map)[proto.basic().mapped_name()] = attrib;
        }

        void RetrieveShaderUniformInfo(
            const ShaderUniformProto& proto, UniformMap* map)
        {
            sh::Uniform uniform;
            RetrieveShaderVariableInfo(proto.basic(), &uniform);
            (*map)[proto.basic().mapped_name()] = uniform;
        }

        void RetrieveShaderVaryingInfo(
            const ShaderVaryingProto& proto, VaryingMap* map)
        {
            sh::Varying varying;
            RetrieveShaderVariableInfo(proto.basic(), &varying);
            varying.interpolation = static_cast<sh::InterpolationType>(
                proto.interpolation());
            varying.isInvariant = proto.is_invariant();
            (*map)[proto.basic().mapped_name()] = varying;
        }

        void RunShaderCallback(const ShaderCacheCallback& callback,
            GpuProgramProto* proto,
            std::string sha_string)
        {
            std::string shader;
            proto->SerializeToString(&shader);

            std::string key;
            base::Base64Encode(sha_string, &key);
            callback.Run(key, shader);
        }

    } // namespace

    MemoryProgramCache::MemoryProgramCache()
        : max_size_bytes_(GetCacheSizeBytes())
        , curr_size_bytes_(0)
        , store_(ProgramMRUCache::NO_AUTO_EVICT)
    {
    }

    MemoryProgramCache::MemoryProgramCache(const size_t max_cache_size_bytes)
        : max_size_bytes_(max_cache_size_bytes)
        , curr_size_bytes_(0)
        , store_(ProgramMRUCache::NO_AUTO_EVICT)
    {
    }

    MemoryProgramCache::~MemoryProgramCache() { }

    void MemoryProgramCache::ClearBackend()
    {
        store_.Clear();
        DCHECK_EQ(0U, curr_size_bytes_);
    }

    ProgramCache::ProgramLoadResult MemoryProgramCache::LoadLinkedProgram(
        GLuint program,
        Shader* shader_a,
        Shader* shader_b,
        const LocationMap* bind_attrib_location_map,
        const std::vector<std::string>& transform_feedback_varyings,
        GLenum transform_feedback_buffer_mode,
        const ShaderCacheCallback& shader_callback)
    {
        char a_sha[kHashLength];
        char b_sha[kHashLength];
        DCHECK(shader_a && !shader_a->last_compiled_source().empty() && shader_b && !shader_b->last_compiled_source().empty());
        ComputeShaderHash(
            shader_a->last_compiled_signature(), a_sha);
        ComputeShaderHash(
            shader_b->last_compiled_signature(), b_sha);

        char sha[kHashLength];
        ComputeProgramHash(a_sha,
            b_sha,
            bind_attrib_location_map,
            transform_feedback_varyings,
            transform_feedback_buffer_mode,
            sha);
        const std::string sha_string(sha, kHashLength);

        ProgramMRUCache::iterator found = store_.Get(sha_string);
        if (found == store_.end()) {
            return PROGRAM_LOAD_FAILURE;
        }
        const scoped_refptr<ProgramCacheValue> value = found->second;
        glProgramBinary(program,
            value->format(),
            static_cast<const GLvoid*>(value->data()),
            value->length());
        GLint success = 0;
        glGetProgramiv(program, GL_LINK_STATUS, &success);
        if (success == GL_FALSE) {
            return PROGRAM_LOAD_FAILURE;
        }
        shader_a->set_attrib_map(value->attrib_map_0());
        shader_a->set_uniform_map(value->uniform_map_0());
        shader_a->set_varying_map(value->varying_map_0());
        shader_b->set_attrib_map(value->attrib_map_1());
        shader_b->set_uniform_map(value->uniform_map_1());
        shader_b->set_varying_map(value->varying_map_1());

        if (!shader_callback.is_null() && !base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuShaderDiskCache)) {
            scoped_ptr<GpuProgramProto> proto(
                GpuProgramProto::default_instance().New());
            proto->set_sha(sha, kHashLength);
            proto->set_format(value->format());
            proto->set_program(value->data(), value->length());

            FillShaderProto(proto->mutable_vertex_shader(), a_sha, shader_a);
            FillShaderProto(proto->mutable_fragment_shader(), b_sha, shader_b);
            RunShaderCallback(shader_callback, proto.get(), sha_string);
        }

        return PROGRAM_LOAD_SUCCESS;
    }

    void MemoryProgramCache::SaveLinkedProgram(
        GLuint program,
        const Shader* shader_a,
        const Shader* shader_b,
        const LocationMap* bind_attrib_location_map,
        const std::vector<std::string>& transform_feedback_varyings,
        GLenum transform_feedback_buffer_mode,
        const ShaderCacheCallback& shader_callback)
    {
        GLenum format;
        GLsizei length = 0;
        glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH_OES, &length);
        if (length == 0 || static_cast<unsigned int>(length) > max_size_bytes_) {
            return;
        }
        scoped_ptr<char[]> binary(new char[length]);
        glGetProgramBinary(program,
            length,
            NULL,
            &format,
            binary.get());
        UMA_HISTOGRAM_COUNTS("GPU.ProgramCache.ProgramBinarySizeBytes", length);

        char a_sha[kHashLength];
        char b_sha[kHashLength];
        DCHECK(shader_a && !shader_a->last_compiled_source().empty() && shader_b && !shader_b->last_compiled_source().empty());
        ComputeShaderHash(
            shader_a->last_compiled_signature(), a_sha);
        ComputeShaderHash(
            shader_b->last_compiled_signature(), b_sha);

        char sha[kHashLength];
        ComputeProgramHash(a_sha,
            b_sha,
            bind_attrib_location_map,
            transform_feedback_varyings,
            transform_feedback_buffer_mode,
            sha);
        const std::string sha_string(sha, sizeof(sha));

        UMA_HISTOGRAM_COUNTS("GPU.ProgramCache.MemorySizeBeforeKb",
            curr_size_bytes_ / 1024);

        // Evict any cached program with the same key in favor of the least recently
        // accessed.
        ProgramMRUCache::iterator existing = store_.Peek(sha_string);
        if (existing != store_.end())
            store_.Erase(existing);

        while (curr_size_bytes_ + length > max_size_bytes_) {
            DCHECK(!store_.empty());
            store_.Erase(store_.rbegin());
        }

        if (!shader_callback.is_null() && !base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuShaderDiskCache)) {
            scoped_ptr<GpuProgramProto> proto(
                GpuProgramProto::default_instance().New());
            proto->set_sha(sha, kHashLength);
            proto->set_format(format);
            proto->set_program(binary.get(), length);

            FillShaderProto(proto->mutable_vertex_shader(), a_sha, shader_a);
            FillShaderProto(proto->mutable_fragment_shader(), b_sha, shader_b);
            RunShaderCallback(shader_callback, proto.get(), sha_string);
        }

        store_.Put(sha_string,
            new ProgramCacheValue(length,
                format,
                binary.release(),
                sha_string,
                a_sha,
                shader_a->attrib_map(),
                shader_a->uniform_map(),
                shader_a->varying_map(),
                b_sha,
                shader_b->attrib_map(),
                shader_b->uniform_map(),
                shader_b->varying_map(),
                this));

        UMA_HISTOGRAM_COUNTS("GPU.ProgramCache.MemorySizeAfterKb",
            curr_size_bytes_ / 1024);
    }

    void MemoryProgramCache::LoadProgram(const std::string& program)
    {
        scoped_ptr<GpuProgramProto> proto(GpuProgramProto::default_instance().New());
        if (proto->ParseFromString(program)) {
            AttributeMap vertex_attribs;
            UniformMap vertex_uniforms;
            VaryingMap vertex_varyings;
            for (int i = 0; i < proto->vertex_shader().attribs_size(); i++) {
                RetrieveShaderAttributeInfo(proto->vertex_shader().attribs(i),
                    &vertex_attribs);
            }
            for (int i = 0; i < proto->vertex_shader().uniforms_size(); i++) {
                RetrieveShaderUniformInfo(proto->vertex_shader().uniforms(i),
                    &vertex_uniforms);
            }
            for (int i = 0; i < proto->vertex_shader().varyings_size(); i++) {
                RetrieveShaderVaryingInfo(proto->vertex_shader().varyings(i),
                    &vertex_varyings);
            }

            AttributeMap fragment_attribs;
            UniformMap fragment_uniforms;
            VaryingMap fragment_varyings;
            for (int i = 0; i < proto->fragment_shader().attribs_size(); i++) {
                RetrieveShaderAttributeInfo(proto->fragment_shader().attribs(i),
                    &fragment_attribs);
            }
            for (int i = 0; i < proto->fragment_shader().uniforms_size(); i++) {
                RetrieveShaderUniformInfo(proto->fragment_shader().uniforms(i),
                    &fragment_uniforms);
            }
            for (int i = 0; i < proto->fragment_shader().varyings_size(); i++) {
                RetrieveShaderVaryingInfo(proto->fragment_shader().varyings(i),
                    &fragment_varyings);
            }

            scoped_ptr<char[]> binary(new char[proto->program().length()]);
            memcpy(binary.get(), proto->program().c_str(), proto->program().length());

            store_.Put(proto->sha(),
                new ProgramCacheValue(proto->program().length(),
                    proto->format(),
                    binary.release(),
                    proto->sha(),
                    proto->vertex_shader().sha().c_str(),
                    vertex_attribs,
                    vertex_uniforms,
                    vertex_varyings,
                    proto->fragment_shader().sha().c_str(),
                    fragment_attribs,
                    fragment_uniforms,
                    fragment_varyings,
                    this));

            UMA_HISTOGRAM_COUNTS("GPU.ProgramCache.MemorySizeAfterKb",
                curr_size_bytes_ / 1024);
        } else {
            LOG(ERROR) << "Failed to parse proto file.";
        }
    }

    MemoryProgramCache::ProgramCacheValue::ProgramCacheValue(
        GLsizei length,
        GLenum format,
        const char* data,
        const std::string& program_hash,
        const char* shader_0_hash,
        const AttributeMap& attrib_map_0,
        const UniformMap& uniform_map_0,
        const VaryingMap& varying_map_0,
        const char* shader_1_hash,
        const AttributeMap& attrib_map_1,
        const UniformMap& uniform_map_1,
        const VaryingMap& varying_map_1,
        MemoryProgramCache* program_cache)
        : length_(length)
        , format_(format)
        , data_(data)
        , program_hash_(program_hash)
        , shader_0_hash_(shader_0_hash, kHashLength)
        , attrib_map_0_(attrib_map_0)
        , uniform_map_0_(uniform_map_0)
        , varying_map_0_(varying_map_0)
        , shader_1_hash_(shader_1_hash, kHashLength)
        , attrib_map_1_(attrib_map_1)
        , uniform_map_1_(uniform_map_1)
        , varying_map_1_(varying_map_1)
        , program_cache_(program_cache)
    {
        program_cache_->curr_size_bytes_ += length_;
        program_cache_->LinkedProgramCacheSuccess(program_hash);
    }

    MemoryProgramCache::ProgramCacheValue::~ProgramCacheValue()
    {
        program_cache_->curr_size_bytes_ -= length_;
        program_cache_->Evict(program_hash_);
    }

} // namespace gles2
} // namespace gpu
